package net.homelinux.chaoswg.io.renga.core.gui.operations.visualisations;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Observable;

import net.homelinux.chaoswg.io.renga.core.gui.operations.helper.Connector;

import org.jdom.Element;

public abstract class Visualisation extends Observable{
    protected Rectangle area = new Rectangle();

    protected Connector inputConnectors [];
    protected Connector outputConnectors [];

    protected boolean selected = false;
    protected boolean error = false;
    protected boolean highlighted = false;

    public final void drawYourself (final Graphics g) {
        if (error) {
            drawError (g);
        }
        
        if (highlighted) {
            drawHighlighted (g);
        } else if (selected) {
            drawSelected (g);
        }

        draw(g);
        drawConnectors (g);
    }
    
    public final void drawHighlighted(final Graphics g) {
        g.setColor(Color.BLUE);
        g.fillRect(this.area.x - 3, this.area.y - 3 , this.area.width + 6, this.area.height + 6);
        g.setColor(Color.BLACK);
    }
    
    public void drawSelected (final Graphics g) {
        g.setColor(Color.RED);
        g.fillRect(this.area.x-1, this.area.y-1, this.area.width+2, this.area.height+2);
        g.setColor(Color.BLACK);
    }
    
    public final void drawError (final Graphics g) {
        g.fillRect(this.area.x - 6, this.area.y - 6, this.area.width + 11, this.area.height + 11);
    }

    public abstract void draw (final Graphics g);

    public void drawConnectors (final Graphics g) {
        if (inputConnectors != null) {
            for (final Connector c : inputConnectors) {
                c.drawYourself(g);
            }
        }
        if (outputConnectors != null) {
            for (final Connector c : outputConnectors) {
                c.drawYourself(g);
            }
        }
    }

    public final Point getPosition () {
        return this.area.getLocation();
    }

    public final int getWidth () {
        return this.area.width;
    }

    public final int getHeight () {
        return this.area.height;
    }

    public Point getConnectorCenter (final int i, final Connector [] connectors) {
        if (connectors != null && i < connectors.length && connectors[i] != null) {
            return connectors[i].getCenter();
        }
        return null;
    }

    public final Point getInputConnectorCenter (final int i) {
        return getConnectorCenter(i, inputConnectors);
    }

    public final Point getOutputConnectorCenter (final int i) {
        return getConnectorCenter(i, outputConnectors);
    }

    public final boolean contains (final Point p) {
        return area.contains(p) || connectorContains(p, inputConnectors) || connectorContains(p, outputConnectors);
    }

    public final void setSelected (final boolean selected) { 
        this.selected = selected;
        
        setChanged();
        notifyObservers(this);
    }

    public final boolean isSelected () {
        return this.selected;
    }

    public final void moveBy (final int dx, final int dy) {
        this.area.x += dx;
        this.area.y += dy;
        moveConnectors (dx, dy, inputConnectors);
        moveConnectors (dx, dy, outputConnectors);

        setChanged();
        notifyObservers(this);
    }

    private final void moveConnectors (final int dx, final int dy, final Connector [] toMove) {
        if (toMove == null) return;
        for (final Connector c : toMove) {
            c.moveBy (dx, dy);
        }
    }

    public final void moveTo (final int x, final int y) {
        final int dx = x - this.area.x;
        final int dy = y - this.area.y;

        moveBy(dx, dy);

        setChanged();
        notifyObservers(this);
    }

    public void setInputs (final int input) {
        if (input > 0) {
            int inputsDistance = this.getWidth() / input;
            this.inputConnectors = new Connector[input];
            for (int i = 0, n = inputConnectors.length; i < n; ++i) {
                inputConnectors[i] = new Connector(Connector.ConnectorType.INPUT);
                inputConnectors[i].setCenter(this.area.x + inputsDistance/2 + i * inputsDistance , this.area.y);
            }
        } else if (input == 0) {
            this.inputConnectors = new Connector[0];
        }
        
        setChanged();
        notifyObservers(this);
    }

    public void setOutputs (final int output) {
        if (output > 0) {
            int outputsDistance = this.getWidth() / output;
            this.outputConnectors = new Connector[output];
            for (int i = 0, n = outputConnectors.length; i < n; ++i) {
                outputConnectors[i] = new Connector(Connector.ConnectorType.OUTPUT);
                outputConnectors[i].setCenter(this.area.x + outputsDistance/2 + i * outputsDistance, this.area.y + this.getHeight());
            }
        } else if (output == 0) {
            this.outputConnectors = new Connector[0];
        }
        
        setChanged();
        notifyObservers(this);

    }

    public boolean connectAllowed (final Point p) {
        return connectorContains(p, inputConnectors) || connectorContains(p, outputConnectors);
    }

    private boolean connectorContains (final Point p, final Connector [] connectors) {
        if (connectors == null) return false;
        for (final Connector c : connectors) {
            if (c.contains(p)) {
                return true;
            }
        }
        return false;
    }

    public int getInputConnectorIndex (final Point p) {
        return getConnectorIndex(p, inputConnectors);
    }

    public int getOutputConnectorIndex (final Point p) {
        return getConnectorIndex(p, outputConnectors);
    }

    public int getConnectorIndex (final Point p, final Connector [] connectors) {
        if (connectors == null) return -1;
        for (int i = 0, n = connectors.length; i < n; ++i) {
            if (connectors[i].contains(p)) {
                return i;
            }
        }
        return -1;
    }
    
    public int getMaxX () {
        return this.area.x + this.area.width;
    }

    public int getMaxY () {
        return this.area.y + this.area.height;
    }
    
    public void setError (final boolean error) {
        this.error = error;
    }

    public Element writeYourself() {
        final Element toReturn = new Element ("visualisation");
        toReturn.setAttribute("type", this.getClass().getCanonicalName());
        final Element x = new Element ("x");
        final Element y = new Element ("y");
        final Element width = new Element ("width");
        final Element height = new Element ("height");

        x.setText(this.area.x+"");
        y.setText(this.area.y+"");
        width.setText(this.area.width+"");
        height.setText(this.area.height+"");
        
        toReturn.addContent(x);
        toReturn.addContent(y);
        toReturn.addContent(width);
        toReturn.addContent(height);

        return toReturn; 
    }

    public boolean isHighlighted() {
        return highlighted;
    }

    public void setHighlighted(boolean highlighted) {
        this.highlighted = highlighted;
    }
}
